package com.fuyao.cloud.admin.flow.controller;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.fuyao.cloud.admin.flow.constant.FlowConstant;
import com.fuyao.cloud.admin.flow.dto.CompleteTaskDTO;
import com.fuyao.cloud.admin.flow.dto.MoveActivityDTO;
import com.fuyao.cloud.admin.flow.dto.ParamVo;
import com.fuyao.cloud.admin.flow.dto.StartProcessDTO;
import com.fuyao.cloud.admin.flow.model.CommentInfo;
import com.fuyao.cloud.admin.flow.model.ExtendHisprocinst;
import com.fuyao.cloud.admin.flow.model.ExtendProcinst;
import com.fuyao.cloud.admin.flow.model.FlowableActHiActinst;
import com.fuyao.cloud.admin.flow.service.flowable.IExtendHisprocinstService;
import com.fuyao.cloud.admin.flow.service.flowable.IExtendProcinstService;
import com.fuyao.cloud.admin.flow.service.flowable.IFlowableActHiActinstService;
import com.fuyao.cloud.admin.flow.service.org.ICommentInfoService;
import com.fuyao.cloud.admin.flow.util.DeleteFlowableProcessInstanceCmd;
import com.fuyao.cloud.admin.flow.util.FlowableHelper;
import com.fuyao.cloud.common.core.flowable.enums.CommentTypeEnum;
import com.fuyao.cloud.common.core.flowable.enums.ProcessStatusEnum;
import com.fuyao.cloud.common.core.util.R;
import com.fuyao.cloud.common.data.tenant.TenantContextHolder;
import com.fuyao.cloud.common.security.annotation.Inner;
import com.fuyao.cloud.common.security.util.SecurityUtils;
import com.fuyao.cloud.flowable.vo.StartProcessInstanceVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.flowable.bpmn.model.*;
import org.flowable.common.engine.api.FlowableException;
import org.flowable.common.engine.impl.interceptor.Command;
import org.flowable.common.engine.impl.interceptor.CommandContext;
import org.flowable.engine.*;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.impl.util.CommandContextUtil;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.identitylink.api.IdentityLinkInfo;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskInfo;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.task.service.HistoricTaskService;
import org.flowable.task.service.impl.persistence.entity.HistoricTaskInstanceEntity;
import org.springframework.beans.BeanUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;

import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;

@RestController
@RequiredArgsConstructor
@Api(value = "flowable", tags = "流程运行时操作")
@RequestMapping("/flowable/runtime")
public class FlowableRuntimeController {

    private final ICommentInfoService commentInfoService;
    private final IExtendHisprocinstService extendHisprocinstService;
    private final IExtendProcinstService extendProcinstService;
    private final IFlowableActHiActinstService flowableActHiActinstService;

    private final RepositoryService repositoryService;
    private final TaskService taskService;
    private final RuntimeService runtimeService;
    private final HistoryService historyService;
    private final IdentityService identityService;

    private final ManagementService managementService;

    @ApiOperation(value = "启动流程")
    @ApiImplicitParam(name = "params", value = "启动参数", required = true, dataTypeClass = StartProcessInstanceVo.class, paramType = "body")
    @PostMapping(value = "/startProcess")
    public R<String> startProcess(@RequestBody StartProcessDTO params) {
        String author = SecurityUtils.getUser().getUsername();
        ProcessDefinition processDefinition = repositoryService
                .createProcessDefinitionQuery()
                .processDefinitionTenantId(TenantContextHolder.getTenantId().toString())
                .processDefinitionKey(params.getProcessDefinitionKey())
                .latestVersion()
                .singleResult();
        if (processDefinition != null && processDefinition.isSuspended()) {
            return R.failed("此流程已经挂起,请联系系统管理员!");
        }

        HistoricProcessInstance historicProcessInstance = historyService
                .createHistoricProcessInstanceQuery()
                .processInstanceBusinessKey(params.getBusinessKey())
                .singleResult();
        if (historicProcessInstance == null) {
            identityService.setAuthenticatedUserId(author);
            ProcessInstance instance = runtimeService
                    .createProcessInstanceBuilder()
                    .processDefinitionKey(params.getProcessDefinitionKey())
                    .name(params.getTitle())
                    .owner(author)
                    .assignee(author)
                    .businessKey(params.getBusinessKey())
                    .variables(params.getVariables())
                    .transientVariable("author", author)
                    .tenantId(TenantContextHolder.getTenantId().toString())
                    .start();

            this.createExtendProcInst(params, instance, author, params.getFormId(), TenantContextHolder.getTenantId().toString());

            identityService.setAuthenticatedUserId(null);
            return R.ok(instance.getProcessInstanceId());
        } else {
            return R.failed("流程已关联");
        }
    }

    /**
     * 添加流程实例额外信息
     *
     * @param startProcessDTO 启动参数
     * @param processInstance 流程参数
     * @param creator         创建人
     * @param formId          表单 id
     * @param tenantId        租户 id
     */
    private void createExtendProcInst(StartProcessDTO startProcessDTO, ProcessInstance processInstance, String creator, String formId, String tenantId) {
        ExtendProcinst extendProcinst = new ExtendProcinst();
        extendProcinst.setFormId(formId);
        extendProcinst.setCurrentUserCode(creator);
        extendProcinst.setProcessInstanceId(processInstance.getId());
        extendProcinst.setProcessDefinitionId(processInstance.getProcessDefinitionId());
        extendProcinst.setProcessStatus(ProcessStatusEnum.SPZ.toString());
        extendProcinst.setProcessName(startProcessDTO.getTitle());
        extendProcinst.setModelKey(startProcessDTO.getProcessDefinitionKey());
        extendProcinst.setBusinessKey(startProcessDTO.getBusinessKey());
        extendProcinst.setTenantId(tenantId);
        extendProcinst.setCreator(creator);

        HistoricProcessInstance historicProcessInstance = historyService
                .createHistoricProcessInstanceQuery()
                .processInstanceId(processInstance.getProcessInstanceId())
                .singleResult();

        if (historicProcessInstance.getEndTime() != null) {
            extendProcinst.setFormId(formId);
            extendProcinst.setProcessStatus(ProcessStatusEnum.BJ.toString());
            extendProcinst.setProcessName(startProcessDTO.getTitle());
            extendProcinst.setBusinessKey(startProcessDTO.getBusinessKey());
            extendProcinst.setProcessDefinitionId(historicProcessInstance.getProcessDefinitionId());
            extendProcinst.setModelKey(startProcessDTO.getProcessDefinitionKey());
            extendProcinst.setTenantId(tenantId);
            extendProcinst.setCurrentUserCode(creator);
            extendProcinst.setId(IdWorker.get32UUID());
            Date date = new Date();
            extendProcinst.setCreateTime(date);
            extendProcinst.setUpdateTime(date);

            ExtendHisprocinst extendHisprocinst = new ExtendHisprocinst();

            BeanUtils.copyProperties(extendProcinst, extendHisprocinst);

            extendHisprocinstService.save(extendHisprocinst);
        } else {
            extendProcinstService.saveExtendProcinstAndHis(extendProcinst);
        }
    }

    @ApiOperation(value = "完成任务")
    @ApiImplicitParam(name = "params", value = "完成参数", required = true, dataTypeClass = StartProcessInstanceVo.class, paramType = "body")
    @PostMapping(value = "/completeTask")
    public R<String> completeTask(@RequestBody CompleteTaskDTO params) {
        Task task = FlowableHelper.getTask(taskService, params.getTaskId());
        if (task != null) {
            claimTask(task);

            taskService.complete(task.getId(), params.getVariables(), params.transientVariables());
            // taskService.complete(task.getId(), params.getVariables());

            createComment(params, task);
            return R.ok(task.getTaskDefinitionKey());
        } else {
            return R.failed("获取不到任务,请关闭页面后重试!");
        }
    }

    /**
     * 自由发送
     *
     * @param params 发送参数
     */
    @ApiOperation(value = "自由发送", notes = "自由发送")
    @ApiImplicitParam(name = "params", value = "发送参数", required = true, dataTypeClass = ParamVo.class, paramType = "body")
    @PostMapping(value = "/send")
    public R<String> send(@RequestBody CompleteTaskDTO params) {
        Task task = FlowableHelper.getTask(taskService, params.getTaskId());

        if (task != null) {
            claimTask(task);

            List<Execution> executions = runtimeService
                    .createExecutionQuery()
                    .parentId(task.getProcessInstanceId())
                    .list();

            BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
            FlowNode from = (FlowNode) bpmnModel.getFlowElement(task.getTaskDefinitionKey());
            FlowNode to = (FlowNode) bpmnModel.getFlowElement(params.getNextNodeId());
            Map<String, Object> variables = runtimeService.getVariables(task.getProcessInstanceId());
            List<List<FlowElement>> paths = FlowableHelper.findPathsDFS(from, to, variables);

            runtimeService.createChangeActivityStateBuilder()
                    .processVariables(params.transientVariables())
                    .moveExecutionsToSingleActivityId(executions.stream().map(Execution::getId).collect(Collectors.toList()), params.getNextNodeId())
                    .changeState();

            createComment(params, task);

            Optional<List<FlowElement>> path = paths
                    .stream()
                    .filter(elements -> elements.stream().filter(flowElement -> flowElement instanceof UserTask).count() == 2)
                    .findFirst();

            if (path.isPresent()) {
                Set<FlowableActHiActinst> flowableActHiActinsts = new HashSet<>();
                List<FlowElement> flowElements = path.get().stream().filter(flowElement -> !flowElement.getId().equals(from.getId()) && !flowElement.getId().equals(to.getId())).collect(Collectors.toList());
                for (FlowElement flowElement : flowElements) {
                    FlowableActHiActinst flowableActHiActinst = newActHiActInst(flowElement, task);
                    flowableActHiActinsts.add(flowableActHiActinst);
                }
                flowableActHiActinstService.saveBatch(flowableActHiActinsts);
            }

            return R.ok();
        } else {
            return R.failed("获取不到任务,请关闭页面后重试!");
        }
    }

    @Inner(false)
    @ApiOperation(value = "删除流程实例", notes = "删除流程实例")
    @ApiImplicitParam(name = "processInstanceId", value = "流程实例 ID", required = true, dataTypeClass = String.class, paramType = "path")
    @DeleteMapping(value = "/deleteByProcessInstanceId/{processInstanceId}")
    public R<Boolean> deleteByProcessInstanceId(@PathVariable String processInstanceId) {

        if (StrUtil.isBlank(processInstanceId)) {
            return R.ok(Boolean.FALSE, "获取不到流程实例");
        }

        long count = runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).count();
        if (count > 0) {
            DeleteFlowableProcessInstanceCmd cmd = new DeleteFlowableProcessInstanceCmd(processInstanceId, "删除流程实例", true);
            managementService.executeCommand(cmd);
        } else {
            historyService.deleteHistoricProcessInstance(processInstanceId);
        }
        return R.ok(Boolean.TRUE);
    }

    @Inner(false)
    @ApiOperation(value = "删除流程实例", notes = "删除流程实例")
    @ApiImplicitParam(name = "businessKey", value = "业务 businessKey", required = true, dataTypeClass = String.class, paramType = "path")
    @DeleteMapping(value = "/deleteByBusinessKey/{businessKey}")
    public R<Boolean> deleteByBusinessKey(@PathVariable String businessKey) {

        ProcessInstance processInstance = runtimeService
                .createProcessInstanceQuery()
                .processInstanceBusinessKey(businessKey)
                .singleResult();
        String processInstanceId = "";

        if (processInstance == null) {
            HistoricProcessInstance historicProcessInstance = historyService
                    .createHistoricProcessInstanceQuery()
                    .processInstanceBusinessKey(businessKey)
                    .singleResult();
            if (historicProcessInstance != null) {
                processInstanceId = historicProcessInstance.getId();
            }
        } else {
            processInstanceId = processInstance.getProcessInstanceId();
        }

        return deleteByProcessInstanceId(processInstanceId);
    }

    public static class MarkTaskCmd implements Command<List<String>>, Serializable {
        protected List<HistoricTaskInstance> historicTaskInstances;
        protected String reason;

        public MarkTaskCmd(List<HistoricTaskInstance> historicTaskInstances, String reason) {
            this.historicTaskInstances = historicTaskInstances;
            this.reason = reason;
        }

        @Override
        public List<String> execute(CommandContext commandContext) {
            if (historicTaskInstances == null) {
                throw new FlowableException("HistoricTaskInstances Cannot Be Empty");
            }
            HistoricTaskService historicTaskService = CommandContextUtil.getHistoricTaskService();
            for (HistoricTaskInstance taskInstance : historicTaskInstances) {
                HistoricTaskInstanceEntity taskInstanceEntity = (HistoricTaskInstanceEntity) taskInstance;
                String deleteReason = taskInstanceEntity.getDeleteReason();
                if (!StringUtils.hasLength(deleteReason)) {
                    taskInstanceEntity.setDeleteReason(this.reason);
                    historicTaskService.updateHistoricTask(taskInstanceEntity, false);
                    continue;
                }
                if (!StrUtil.containsAny(deleteReason, FlowConstant.WITHDRAW_DELETE_REASON) || !deleteReason.contains(FlowConstant.REJECT_DELETE_REASON)) {
                    taskInstanceEntity.setDeleteReason(deleteReason + "|" + this.reason);
                    historicTaskService.updateHistoricTask(taskInstanceEntity, false);
                }
            }

            return this.historicTaskInstances.stream().map(TaskInfo::getExecutionId).collect(Collectors.toList());
        }
    }

    @ApiOperation(value = "提取", notes = "提取")
    @ApiImplicitParam(name = "params", value = "提取参数", required = true, dataTypeClass = ParamVo.class, paramType = "body")
    @PostMapping(value = "/withdraw")
    public R<String> withdraw(@RequestBody MoveActivityDTO params) {
        TaskInfo task = historyService.createHistoricTaskInstanceQuery().taskId(params.getTaskId()).singleResult();

        if (task != null) {
            List<Execution> executions = runtimeService
                    .createExecutionQuery()
                    .parentId(task.getProcessInstanceId())
                    .list();

            List<String> nodeIds = Collections.singletonList(task.getTaskDefinitionKey());

            String author = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult().getStartUserId();

            List<HistoricTaskInstance> historicTaskInstances = historyService
                    .createHistoricTaskInstanceQuery()
                    .processInstanceId(task.getProcessInstanceId())
                    .includeIdentityLinks()
                    .taskDefinitionKeys(nodeIds)
                    .list();

            Set<String> handlers = new HashSet<>();
            historicTaskInstances.forEach(historicTaskInstance -> {
                String assignee = historicTaskInstance.getAssignee();
                if (assignee != null) {
                    handlers.add(assignee);
                } else { // 单任务实例
                    List<? extends IdentityLinkInfo> identityLinks = historicTaskInstance.getIdentityLinks();
                    if (!identityLinks.isEmpty()) {
                        handlers.addAll(identityLinks.stream().map(IdentityLinkInfo::getUserId).collect(Collectors.toSet()));
                    }
                }
            });

            runtimeService.createChangeActivityStateBuilder()
                    .processVariable("handlers", handlers)
                    .processVariable("author", author)
                    .moveExecutionsToSingleActivityId(executions.stream().map(Execution::getId).collect(Collectors.toList()), task.getTaskDefinitionKey())
                    .changeState();

            // 避免在同一个事务里更新同样的历史任务引用,这里必须重新查一次
            historicTaskInstances = historyService
                    .createHistoricTaskInstanceQuery()
                    .processInstanceId(task.getProcessInstanceId())
                    .taskDefinitionKeys(nodeIds)
                    .finished()
                    .list();
            managementService.executeCommand(new MarkTaskCmd(historicTaskInstances, FlowConstant.WITHDRAW_DELETE_REASON));

            CommentInfo commentInfo = new CommentInfo(CommentTypeEnum.TQ.name(), SecurityUtils.getUser().getUsername(), task.getProcessInstanceId(), params.getAdvice());
            commentInfo.setTaskId(task.getId());
            commentInfo.setActivityId(task.getTaskDefinitionKey());
            commentInfo.setActivityName(task.getName());
            commentInfo.setPersonalName(SecurityUtils.getUser().getName());
            commentInfoService.saveComment(commentInfo);
            return R.ok();
        } else {
            return R.failed("获取不到任务,请关闭页面后重试!");
        }
    }

    @ApiOperation(value = "退回", notes = "退回")
    @ApiImplicitParam(name = "params", value = "退回参数", required = true, dataTypeClass = ParamVo.class, paramType = "body")
    @PostMapping(value = "/reject")
    public R<String> reject(@RequestBody MoveActivityDTO params) {
        Task task = FlowableHelper.getTask(taskService, params.getTaskId());

        if (task != null) {
            List<Execution> executions = runtimeService
                    .createExecutionQuery()
                    .parentId(task.getProcessInstanceId())
                    .list();

            BpmnModel bpmnModel = repositoryService.getBpmnModel(task.getProcessDefinitionId());
            FlowNode from = (FlowNode) bpmnModel.getFlowElement(params.getTargetNodeId());
            FlowNode to = (FlowNode) bpmnModel.getFlowElement(task.getTaskDefinitionKey());
            Map<String, Object> variables = runtimeService.getVariables(task.getProcessInstanceId());
            List<List<FlowElement>> paths = FlowableHelper.findPathsDFS(from, to, variables);
            Set<String> nodeIds = paths.stream()
                    .flatMap(Collection::stream)
                    .filter(flowElement -> flowElement instanceof UserTask)
                    .map(BaseElement::getId)
                    .collect(Collectors.toSet());

            String author = runtimeService.createProcessInstanceQuery().processInstanceId(task.getProcessInstanceId()).singleResult().getStartUserId();
            List<HistoricTaskInstance> historicTaskInstances = historyService
                    .createHistoricTaskInstanceQuery()
                    .processInstanceId(task.getProcessInstanceId())
                    .includeIdentityLinks()
                    .taskDefinitionKeys(Collections.singletonList(task.getTaskDefinitionKey()))
                    .list();

            Set<String> handlers = new HashSet<>();
            historicTaskInstances.forEach(historicTaskInstance -> {
                String assignee = historicTaskInstance.getAssignee();
                if (assignee != null) {
                    handlers.add(assignee);
                } else { // 单任务实例
                    List<? extends IdentityLinkInfo> identityLinks = historicTaskInstance.getIdentityLinks();
                    if (!identityLinks.isEmpty()) {
                        handlers.addAll(identityLinks.stream().map(IdentityLinkInfo::getUserId).collect(Collectors.toSet()));
                    }
                }
            });

            runtimeService.createChangeActivityStateBuilder()
                    .processVariable("handlers", handlers)
                    .processVariable("author", author)
                    .moveExecutionsToSingleActivityId(executions.stream().map(Execution::getId).collect(Collectors.toList()), params.getTargetNodeId())
                    .changeState();

            // 避免在同一个事务里更新同样的历史任务引用,这里必须重新查一次
            historicTaskInstances = historyService
                    .createHistoricTaskInstanceQuery()
                    .processInstanceId(task.getProcessInstanceId())
                    .taskDefinitionKeys(nodeIds)
                    .finished()
                    .list();

            managementService.executeCommand(new MarkTaskCmd(historicTaskInstances, FlowConstant.REJECT_DELETE_REASON));

            CommentInfo commentInfo = new CommentInfo(CommentTypeEnum.TH.name(), SecurityUtils.getUser().getUsername(), task.getProcessInstanceId(), params.getAdvice());
            commentInfo.setTaskId(task.getId());
            commentInfo.setActivityId(task.getTaskDefinitionKey());
            commentInfo.setActivityName(task.getName());
            commentInfo.setPersonalName(SecurityUtils.getUser().getName());
            commentInfoService.saveComment(commentInfo);
            return R.ok();
        } else {
            return R.failed("获取不到任务,请关闭页面后重试!");
        }

    }

    private void createComment(@RequestBody CompleteTaskDTO params, Task task) {
        CommentInfo commentInfo = new CommentInfo(CommentTypeEnum.SP.name(), SecurityUtils.getUser().getUsername(), task.getProcessInstanceId(), params.getAdvice());
        commentInfo.setTaskId(task.getId());
        commentInfo.setActivityId(task.getTaskDefinitionKey());
        commentInfo.setActivityName(task.getName());
        commentInfo.setPersonalName(SecurityUtils.getUser().getName());
        commentInfoService.saveComment(commentInfo);
    }

    private FlowableActHiActinst newActHiActInst(FlowElement flowElement, Task task) {
        FlowableActHiActinst actHiActInst = new FlowableActHiActinst();
//        actHiActInst.setDeleteReason("Fast forward to " + next);
        actHiActInst.setId(UUID.randomUUID().toString());
        actHiActInst.setActId(flowElement.getId());
        actHiActInst.setActName(flowElement.getName());
        actHiActInst.setActType(StrUtil.lowerFirst(flowElement.getClass().getSimpleName()));
        actHiActInst.setRev(1);
        actHiActInst.setProcInstId(task.getProcessInstanceId());
        actHiActInst.setTaskId(task.getId());
        actHiActInst.setProcDefId(task.getProcessDefinitionId());
        actHiActInst.setExecutionId(task.getExecutionId());
        Date date = new Date();
        actHiActInst.setStartTime(date);
        actHiActInst.setEndTime(date);
        actHiActInst.setTransactionOrder(1);
        actHiActInst.setDuration("0");
        actHiActInst.setTenantId(task.getTenantId());
        return actHiActInst;
    }

    private void claimTask(Task task) {
        String assignee = task.getAssignee();
        String username = SecurityUtils.getUser().getUsername();

        if (!username.equals(assignee)) {
            if (StringUtils.hasText(assignee)) {
                taskService.unclaim(task.getId());
            }
            taskService.claim(task.getId(), username);
        }
    }

}
